package de.mmis.core.tuplespace;

import java.io.IOException;
import java.util.Collection;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeoutException;

import de.mmis.core.base.abstracttree.InnerNode;
import de.mmis.core.base.abstracttree.LeafNode;
import de.mmis.core.base.abstracttree.Tree;

/**
 * map implementation using tuplespace, where operations on different elements
 * are asynchronous. It is faster if values are read and refreshed often, but
 * requires the user to know, which elements are in the map (if an element that
 * is set or get is not part of the map timeout will occur). Should be used only
 * if all elements used are known to be present all the time. Do not use on
 * synchronized map!
 * 
 * @author Christian Koch
 * 
 */
// not re-engineered yet. use sync map instead
@Deprecated
public class TuplespaceAsyncMap extends TuplespaceContainer
		implements
			Map<Tree, Tree> {

	private static final String MAP_ELEM_NAME_SUFFIX = "-map-elem";
	private static final String MAP_NAME_PREFIX = ":";

	private final Tuplespace space;
	private final String name;
	private final LeafNode mapElemName;
	private final Tree id;
	private final InnerNode mapElemTuple;

	/**
	 * creates new tuplespace map
	 * 
	 * @param space
	 *            tuplespace client connected to used server
	 * @param name
	 *            map name (combined name and id should be unique)
	 * @param id
	 *            object id map belongs to (combined name and id should be
	 *            unique)
	 * @throws IOException
	 *             error while reading or writing to tuplespace server
	 */
	public TuplespaceAsyncMap(Tuplespace space, String name, Tree id)
			throws IOException {
		this(space, name, id, Long.MAX_VALUE, Long.MAX_VALUE);
	}

	/**
	 * creates new tuplespace map, that destroy itself when not used for a
	 * specific time
	 * 
	 * @param space
	 *            tuplespace client connected to used server
	 * @param name
	 *            map name (combined name and id should be unique)
	 * @param id
	 *            object id map belongs to (combined name and id should be
	 *            unique)
	 * @param leaseTime
	 *            time to live between two consecutive insert or delete
	 *            operations
	 * @throws IOException
	 *             error while reading or writing to tuplespace server
	 */
	public TuplespaceAsyncMap(Tuplespace space, String name, Tree id,
			long leaseTime) throws IOException {
		this(space, name, id, leaseTime, Long.MAX_VALUE);
	}

	/**
	 * creates new tuplespace map, that destroy itself when not used for a
	 * specific time
	 * 
	 * @param space
	 *            tuplespace client connected to used server
	 * @param name
	 *            map name (combined name and id should be unique)
	 * @param id
	 *            object id map belongs to (combined name and id should be
	 *            unique)
	 * @param leaseTime
	 *            time to live between two consecutive insert or delete
	 *            operations
	 * @param timeout
	 *            default time span to wait trying to take map element tuples
	 * @throws IOException
	 *             error while reading or writing to tuplespace server
	 */
	public TuplespaceAsyncMap(Tuplespace space, String name, Tree id,
			long leaseTime, long timeout) throws IOException {
		super(timeout, leaseTime);
		this.space = space;
		this.name = name;
		this.id = id;
		this.mapElemName = new LeafNode(
				(MAP_NAME_PREFIX + this.name + MAP_ELEM_NAME_SUFFIX));

		this.mapElemTuple = new InnerNode(this.mapElemName, this.id,
				new LeafNode("?"), new LeafNode("?"));
	}

	/**
	 * inserts new element to map (do not use if already present)
	 * 
	 * @param key
	 *            key of map element to add
	 * @param value
	 *            value of map element to add
	 * @throws IOException
	 *             error while reading or writing to tuplespace server
	 */
	public void addValue(Tree key, Tree value) throws IOException {
		addValue(key, value, leaseTime);
	}

	/**
	 * inserts new element to map (do not use if already present)
	 * 
	 * @param key
	 *            key of map element to add
	 * @param value
	 *            value of map element to add
	 * @param leaseTime
	 *            time to live for this element
	 * @throws IOException
	 *             error while reading or writing to tuplespace server
	 */
	public void addValue(Tree key, Tree value, long leaseTime)
			throws IOException {
		try {
			InnerNode temp = new InnerNode(mapElemName, id, key, value);
			space.write(temp, leaseTime);
		} catch (TuplespaceException e) {
			throw new IOException("Tuplespace error", e);
		}
	}

	/**
	 * changes existing map element (will time out if element not present)
	 * 
	 * @param key
	 *            key of map element to change
	 * @param value
	 *            new value
	 * @param timeout
	 *            time span to wait trying to remove map elem tuple
	 * @return previous element value
	 * @throws IOException
	 *             error while reading or writing to tuplespace server
	 * @throws TimeoutException
	 *             timed out while trying to take map element tuple
	 */
	public Tree setValue(Tree key, Tree value, long timeout)
			throws IOException, TimeoutException {
		return setValue(key, value, timeout, leaseTime);
	}

	/**
	 * changes existing map element (will time out if element not present)
	 * 
	 * @param key
	 *            key of map element to change
	 * @param value
	 *            new value
	 * @param timeout
	 *            time span to wait trying to remove map elem tuple
	 * @param leaseTime
	 *            time to live for this element
	 * @return previous element value
	 * @throws IOException
	 *             error while reading or writing to tuplespace server
	 * @throws TimeoutException
	 *             timed out while trying to take map element tuple
	 */
	public Tree setValue(Tree key, Tree value,
			long timeout, long leaseTime) throws IOException, TimeoutException {
		try {
			InnerNode temp = new InnerNode(mapElemName, id, key, new LeafNode("?"));
			Tree ret = space.take(temp, timeout);
			if (ret == null)
				throw new TimeoutException("map elem tuple take timed out");

			temp.setSubTree(3, value);
			space.write(temp, leaseTime);

			return ret;

		} catch (TuplespaceException e) {
			throw new IOException("Tuplespace error", e);
		}
	}

	/**
	 * get value of element with given key (time out if key not exist)
	 * 
	 * @param key
	 *            key to find value for
	 * @param timeout
	 *            time span to wait trying to read map elem tuple
	 * @return value for given key
	 * @throws IOException
	 *             error while reading or writing to tuplespace server
	 * @throws TimeoutException
	 *             timed out while trying to read map element tuple
	 */
	public Tree getValue(Tree key, long timeout)
			throws IOException, TimeoutException {
		try {
			InnerNode temp = new InnerNode(mapElemName, id, key, new LeafNode("?"));
			Tree ret = space.read(temp, timeout);
			if (ret == null)
				throw new TimeoutException("map elem tuple read timed out");
			return ret;
		} catch (TuplespaceException e) {
			throw new IOException("Tuplespace error", e);
		}
	}

	/**
	 * remove element with given key (time out if key not exist)
	 * 
	 * @param key
	 *            key to remove
	 * @param timeout
	 *            time span to wait trying to take map elem tuple
	 * @return value for given key
	 * @throws IOException
	 *             error while reading or writing to tuplespace server
	 * @throws TimeoutException
	 *             timed out while trying to take map element tuple
	 */
	public Tree remove(Tree key, long timeout)
			throws IOException, TimeoutException {
		try {
			InnerNode temp = new InnerNode(mapElemName, id, key, new LeafNode("?"));
			Tree ret = space.take(temp, timeout);
			if (ret == null)
				throw new TimeoutException("map elem tuple take timed out");
			return ret;
		} catch (TuplespaceException e) {
			throw new IOException("Tuplespace error", e);
		}
	}

	/**
	 * removes all currently existing keys (maybe not all if some were taken by
	 * other clients)
	 * 
	 * @throws IOException
	 *             error while reading or writing to tuplespace server
	 */
	public void removeAll() throws IOException {
		try {
			while (space.takeIfExists(mapElemTuple) != null) {
				// take until empty
			}
		} catch (TuplespaceException e) {
			throw new IOException("Tuplespace error", e);
		}
	}

	@Override
	public void clear() {
		try {
			removeAll();
		} catch (IOException e) {
			throw new IllegalStateException(e);
		}
	}

	@Override
	public boolean containsKey(Object key) {
		try {
			if (!(key instanceof Tree))
				return false;
			if (getValue((Tree) key, getTimeout()) == null)
				return false;

			return true;
		} catch (IOException e) {
			throw new IllegalStateException(e);
		} catch (TimeoutException e) {
			throw new IllegalStateException(e);
		}
	}

	@Override
	public boolean containsValue(Object value) {
		throw new UnsupportedOperationException(
				"not allowed in asnyc TuplespaceMap");
	}

	@Override
	public Set<java.util.Map.Entry<Tree, Tree>> entrySet() {
		throw new UnsupportedOperationException("not allowed in TuplespaceMap");
	}

	@Override
	public Tree get(Object key) {
		if (!(key instanceof Tree))
			return null;
		try {
			return getValue((Tree) key, getTimeout());
		} catch (IOException e) {
			throw new IllegalStateException(e);
		} catch (TimeoutException e) {
			throw new IllegalStateException(e);
		}
	}

	@Override
	public boolean isEmpty() {
		throw new UnsupportedOperationException(
				"not allowed in asnyc TuplespaceMap");
	}

	@Override
	public Set<Tree> keySet() {
		throw new UnsupportedOperationException("not allowed in TuplespaceMap");
	}

	@Override
	public Tree put(Tree key, Tree value) {
		try {
			return setValue(key, value, getTimeout());
		} catch (IOException e) {
			throw new IllegalStateException(e);
		} catch (TimeoutException e) {
			throw new IllegalStateException(e);
		}
	}

	@Override
	public void putAll(Map<? extends Tree, ? extends Tree> m) {
		throw new UnsupportedOperationException("not allowed in TuplespaceMap");
	}

	@Override
	public Tree remove(Object key) {
		if (!(key instanceof Tree))
			return null;
		try {
			return remove((Tree) key, getTimeout());
		} catch (IOException e) {
			throw new IllegalStateException(e);
		} catch (TimeoutException e) {
			throw new IllegalStateException(e);
		}
	}

	@Override
	public int size() {
		throw new UnsupportedOperationException("not allowed in TuplespaceMap");
	}

	@Override
	public Collection<Tree> values() {
		throw new UnsupportedOperationException("not allowed in TuplespaceMap");
	}

}
